﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Security;
using SEAM.Core;

namespace SEAM.Exception.Formatter
{
    /// <summary>
    /// Provides base class for excepion formatting operations.
    /// </summary>
    public abstract class ExceptionFormatterBase : IExceptionFormatter
    {
        private const BindingFlags InfoType = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
        private const BindingFlags InstanceInfoType = InfoType | BindingFlags.DeclaredOnly;
        private static string MachineName
        {
            get
            {
                try
                {
                    return Environment.MachineName;
                }
                catch (SecurityException)
                {
                    return "Permission Denied";
                }
            }
        }
        private static string WindowsIdentity
        {
            get
            {
                try
                {
                    return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
                }
                catch (SecurityException)
                {
                    return "Permission Denied";
                }
            }
        }

        /// <summary>
        /// A section that provides global application information
        /// </summary>
        protected virtual IDictionary ApplicationInfo
        {
            get
            {
                ListDictionary retVal = new ListDictionary();
                retVal.Add("MachineName", MachineName);
                retVal.Add("TimeStamp", DateTime.Now.ToString("G"));
                retVal.Add("AppDomainName", AppDomain.CurrentDomain.FriendlyName);
                retVal.Add("ThreadIdentity", Thread.CurrentPrincipal.Identity.Name);
                retVal.Add("WindowsIdentity", WindowsIdentity);
                retVal.Add("FullName", Assembly.GetExecutingAssembly().FullName);
                return retVal;
            }
        }
        /// <summary>
        /// A section that provides information on all Assemblies that have been bound
        /// in current AppDomain
        /// </summary>
        protected virtual IDictionary AssemblyInfo
        {
            get
            {
                SortedDictionary<string, string> retVal = new SortedDictionary<string, string>();
                foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        retVal.Add(asm.FullName, (asm.GlobalAssemblyCache ? "[GAC] " : null) + asm.Location);
                    }
                    catch { }
                }
                return retVal;
            }
        }
 
        /// <summary>
        /// Gets the header for the exception message
        /// </summary>
        /// <returns>a <code>string</code> that represents the header</returns>
        protected virtual string GetHeader() { return null; }
        /// <summary>
        /// Gets the footer for the exception message
        /// </summary>
        /// <returns>a <code>string</code> that represents the footer</returns>
        protected virtual string GetFooter() { return null; }
        /// <summary>
        /// Gets a separator for different section of the exception message
        /// </summary>
        /// <returns>a <code>string</code> that represents a section separator</returns>
        protected virtual string GetSectionSeparator() { return null; }
        /// <summary>
        /// Gets the section for the exception message
        /// </summary>
        /// <param name="sectionName">name of the section</param>
        /// <param name="sectionInfo">information that should be published in this section</param>
        /// <returns>a <code>string</code> that represents the sectionInfo object</returns>
        protected abstract string GetSection(string sectionName, IDictionary sectionInfo);

        #region IExceptionFormatter Members

        public string GetFormattedMessage(System.Exception ex)
        {
            return GetFormattedMessage(ex, null);
        }

        public string GetFormattedMessage(System.Exception ex, IDictionary<string, object> additionalInfo)
        {
            #region Initilize sections to format

            IDictionary<string, IDictionary> exceptionInfo = new Dictionary<string, IDictionary>();
            exceptionInfo.Add("Application Information", ApplicationInfo);            
            AddExceptionInfo(ex, exceptionInfo, exceptionInfo.Count);

            #region Add custom sections

            if (additionalInfo != null && additionalInfo.Count > 0)
            {
                foreach (string key in additionalInfo.Keys)
                    exceptionInfo.Add(key, GetObjectInfo(additionalInfo[key]));
            }
            
            #endregion

            exceptionInfo.Add("Loaded Assemblies", AssemblyInfo);

            #endregion

            StringBuilder retVal = new StringBuilder();

            retVal.Append(GetHeader());

            int count = exceptionInfo.Count;
            if (exceptionInfo != null && count > 0)
            {
                foreach (string key in exceptionInfo.Keys)
                {
                    count--;
                    retVal.Append(GetSection(key, exceptionInfo[key]));

                    if (count > 0)
                        retVal.Append(GetSectionSeparator());
                }
            }

            retVal.Append(GetFooter());

            return retVal.ToString();
        }

        #endregion

        #region Helper Methods

        protected virtual int AddExceptionInfo(System.Exception ex, IDictionary<string, IDictionary> exceptionInfo, int depth)
        {
            if (ex.InnerException != null)
                depth = AddExceptionInfo(ex.InnerException, exceptionInfo, depth);

            ListDictionary objInfo = new ListDictionary();
            objInfo.Add("Timestamp", DateTime.Now.ToString("G"));
            objInfo.Add("Message", ex.Message);
            objInfo.Add("Stack Trace", ex.StackTrace);
            if (ex.GetType() != null)
                objInfo.Add("Exception Type", ex.GetType().FullName);
            if (ex.TargetSite != null)
                objInfo.Add("Source", ex.TargetSite.DeclaringType.FullName + " @ " + ex.TargetSite);

            if (ex is SEAMException)
            {
                IDictionary<string, object> exInfo = ((SEAMException)ex).AdditionalInfo;
                if (exInfo != null && exInfo.Count > 0)
                {
                    foreach (string key in ((SEAMException)ex).AdditionalInfo.Keys)
                        objInfo.Add("." + key, (exInfo[key] == null ? "NULL" : exInfo[key]));
                }
            }

            exceptionInfo.Add("Exception Information [" + depth + "]", objInfo);
            return depth + 1;
        }

        protected virtual IDictionary GetObjectInfo(object objSource)
        {
            return GetObjectInfo(objSource, InfoType);
        }

        protected virtual IDictionary GetObjectInfo(object objSource, BindingFlags objInfo)
        {
            if (objSource == null)
                return null;

            SortedDictionary<string, string> retVal = new SortedDictionary<string, string>();

            if (objSource is IDictionary)
            {
                foreach (object key in ((IDictionary)objSource).Keys)
                {
                    object value = ((IDictionary)objSource)[key];
                    retVal.Add("" + key, "" + value);
                }
            }


            FieldInfo[] objFields = objSource.GetType().GetFields(objInfo);
            foreach (FieldInfo field in objFields)
            {
                object value = field.GetValue(objSource);
                retVal.Add("" + field.Name, "" + value);
            }


            return retVal;
        }

        #endregion
    }
}
